#pragma once
#include "QueryEvaluator.h"
#include "Node.h"
#include <set>
#include <iostream>

QueryEvaluator::QueryEvaluator(AST& ast, DesignEntityTable* deTable, Follows& follows, 
	Modifies& modifies, Parent& parent, Uses& uses, VarTable& varTable) {
		this->ast = ast;
		this->deTable = deTable;
		this->follows = follows;
		this->modifies = modifies;
		this->parent = parent;
		this->uses = uses;
		this->varTable = varTable;

		if(ast.getRoot() == NULL){
			throw CustomException(PQL_PARSE_ERROR, "no AST");
		}
}

list<string> QueryEvaluator::evaluateCompleteQuery(CompleteQuery *completeQuery){
	mapper.clear();
	vector<Synonym*> declaration_parameters = completeQuery->getDeclarationParameters();
	vector<Query*> suchthat_queries = completeQuery->getSuchThatQuery();
	vector<Query*> pattern_queries = completeQuery->getPatternQuery();
	
	Result* result = completeQuery->getResult();
	string res_name = result->getVariable();
	
	bool boolEvaluation = true;
	bool qonefirst = false;
	isAffected = false;
	int overlap1 = 0, overlap2 = 0;

	vector<int> initial = getInitialResultList(result->getVarType());

	if(suchthat_queries.size() != 0 && pattern_queries.size() != 0){
		Query *q1 = suchthat_queries.at(0);
		Query *q2 = pattern_queries.at(0);
		
		string p1name = q1->getFirstArgument()->getVariable();
		string p2name = q1->getSecondArgument()->getVariable();
		string p3name = q2->getFirstArgument()->getVariable();
		string p4name = q2->getSecondArgument()->getVariable();

		if(p1name == result->getVariable()){
			if(p2name == p3name){
				overlap1 = 1;
				overlap2 = 0;
				isAffected = true;
				qonefirst = true;
			} else if(p2name == p4name){
				overlap1 = 1;
				overlap2 = 1;
				isAffected = true;
				qonefirst = true;
			}
		} else if(p2name == result->getVariable()){
			if(p1name == p3name){
				overlap1 = 0;
				overlap2 = 0;
				isAffected = true;
				qonefirst = true;
			} else if(p1name == p4name){
				overlap1 = 0;
				overlap2 = 1;
				isAffected = true;
				qonefirst = true;
			}
		} else if(p3name == result->getVariable()){
			if(p4name == p1name){
				overlap1 = 1;
				overlap2 = 0;
				isAffected = true;
			} else if(p4name == p2name){
				overlap1 = 1;
				overlap2 = 1;
				isAffected = true;
			}
		} else if(p4name == result->getVariable()){
			if(p3name == p1name){
				overlap1 = 0;
				overlap2 = 0;
				isAffected = true;
			} else if(p3name == p2name){
				overlap1 = 0;
				overlap2 = 1;
				isAffected = true;
			}
		}

		if(isAffected){
			vector<pair<int,int>> tmp = evaluateSuchThat(q1);
			VAR_TYPE v1 = q1->getFirstArgument()->getVarType();
			string v1_name = q1->getFirstArgument()->getVariable();

			VAR_TYPE v2 = q1->getSecondArgument()->getVarType();
			string v2_name = q1->getSecondArgument()->getVariable();

			updateMap(v1, v1_name, v2, v2_name, tmp);
			patternholder.clear();
			evaluatePattern(q2);

			/*for(size_t i = 0; i < tmp.size(); i++){
				cout << tmp[i].first << " " << tmp[i].second << endl;
			}
			cout << "-----" << endl;
			for(size_t i = 0; i < patternholder.size(); i++){
				cout << patternholder[i].first << " " << patternholder[i].second << endl;
			}*/
			//cout << overlap1 << " " << overlap2 << endl;
			vector<int> combined;
			if(p1name == p3name && p2name == p4name){
				combined = combineQueriesIdentical(tmp, patternholder, overlap1, overlap2);
			} else if(qonefirst){
				combined = combineQueries(tmp, patternholder, overlap1, overlap2);
			} else{
				combined = combineQueries(patternholder, tmp, overlap1, overlap2);
			}

			set<string> out;
			for(size_t i = 0; i < combined.size(); i++){
				if(result->getVarType() == Q_VARIABLE){ //special case, need to get the name
					out.insert(varTable.getVarName(combined.at(i)));
				} else{
					out.insert(to_string(static_cast<long long>(combined.at(i))));
				}
			}
			list<string> out2(out.begin(), out.end());
			return out2;
		}
	}

	
	//cout << "here" << endl;
	set<string> outputlist;
	for(size_t i = 0; i < suchthat_queries.size(); i++){
		Query *query = suchthat_queries.at(i);
		Synonym* param1 = query->getFirstArgument();
		string param1_name = param1->getVariable();
		VAR_TYPE param1_type = param1->getVarType();
		
		Synonym* param2 = query->getSecondArgument();
		string param2_name = param2->getVariable();
		VAR_TYPE param2_type = param2->getVarType();
		
		if((param1_name == "_" && param2_name == "_") || param1_name != param2_name){ //same name is not allowed except for anyval
			vector<pair<int,int>> tmp = evaluateSuchThat(query);
			//for(int i = 0; i < tmp.size(); i++){
			//	cout << tmp[i].first << " " << tmp[i].second << endl;
			//}
			boolEvaluation = boolEvaluation & updateMap(param1_type, param1_name, param2_type, param2_name, tmp);
			//boolEvaluation = boolEvaluation & updateMap(2, param2_type, param2_name, tmp);
		} else{
			boolEvaluation = false;
		}
	}

	for(size_t i = 0; i < pattern_queries.size(); i++){
		Query *query = pattern_queries.at(i);
		boolEvaluation = boolEvaluation & evaluatePattern(query);
	}
	
	//cout << boolEvaluation << endl;
	if(mapper.find(res_name) != mapper.end() && boolEvaluation == true){ //the result is inside the map
		//cout << "result found in map" << endl;
		vector<int> res_list = intersect(mapper[res_name], initial);
		for(size_t i = 0; i < res_list.size(); i++){
			//cout << res_list.at(i) << endl;
			if(result->getVarType() == Q_VARIABLE){ //special case, need to get the name
				outputlist.insert(varTable.getVarName(res_list.at(i)));
			} else{
				outputlist.insert(to_string(static_cast<long long>(res_list.at(i))));
			}
		}
	} else if(boolEvaluation == true){ //the result is not in the map, but the queries are true, use the initialResult
		for(size_t i = 0; i < initial.size(); i++){
			if(result->getVarType() == Q_VARIABLE){ //special case, need to get the name
				outputlist.insert(varTable.getVarName(initial.at(i)));
			} else{
				outputlist.insert(to_string(static_cast<long long>(initial.at(i))));
			}
		}
	}
	list<string> out(outputlist.begin(), outputlist.end());
	return out;
}

bool QueryEvaluator::evaluatePattern(Query *query){
	QueryType qt = query->getQueryType();
	Synonym* param1 = query->getFirstArgument();
	string param1_name = param1->getVariable();
	VAR_TYPE param1_type = param1->getVarType();

	Synonym* param2 = query->getSecondArgument();
	string param2_name = param2->getVariable();
	string param2_trim = "_";
	VAR_TYPE param2_type = param2->getVarType();

	bool updateVar = false;
	vector<int> varIndex;
	vector<int> result;

	if(param2_type == Q_VARIABLE){
		updateVar = true;
	} else{
		if(param2_name.length() >= 3){
			param2_trim = param2_name.substr(1,param2_name.length()-2);
		}
	}

	Synonym* param3 = query->getThirdArgument();
	string param3_name = param3->getVariable();
	string param3_trim = "_";
	if(param3_name.length() > 4){ //i.e _"x"_
		param3_trim = param3_name.substr(2,param3_name.length()-4);
	}
	
	vector<Node*> tmp = deTable->getAssignDe();
	bool found = false;
	for(size_t i = 0; i < tmp.size(); i++){
		Node *n = tmp.at(i);
		vector<Node*> children = n->getChildren();

		bool rightok = false;
		if(param3_trim == "_" && children.size() >= 2){ //right side is ok
			rightok = true;
		} else{ //check for the right side first
			Node *child = children.at(1);
			DesignEntity *de = child->getDesignEntity();
	
			if(de->getType() == VARIABLE){ // only 1 var on right hand side
				Variable *v = dynamic_cast<Variable*>(de);
				int v_val = v->getIndex();
				if(varTable.getVarName(v_val) == param3_trim){
					rightok = true;
				}
			} else{
				vector<pair<int,int>> pairs = uses.getUsesStatement(n->getStmtNumber(), -1);
				vector<int> vars;
				for(size_t i = 0; i < pairs.size(); i++){
					vars.push_back(pairs[i].second);
				}
				for(size_t i = 0; i < vars.size(); i++){
					if(varTable.getVarName(vars[i]) == param3_trim){
						rightok = true;
						break;
					}
				}
			}
		}
		if(rightok){
			if(updateVar){ //save the left side & assign
				Node *fchild = children.at(0);
				DesignEntity *de = fchild->getDesignEntity();
				Variable *v;
				if(de->getType() == VARIABLE){
					v = dynamic_cast<Variable*>(de);
				} else{
					continue;
				}
				varIndex.push_back(v->getIndex());
				result.push_back(n->getStmtNumber());
				found = true;
			} else if(param2_trim != "_"){ //check for left side
				Node *fchild = children.at(0);
				DesignEntity *de = fchild->getDesignEntity();
				Variable *v;
				if(de->getType() == VARIABLE){
					v = dynamic_cast<Variable*>(de);
				} else{
					continue;
				}
				int v_val = v->getIndex();
				if(varTable.getVarName(v_val) == param2_trim){ //match
					result.push_back(n->getStmtNumber());
					found = true;
				}
			} else{
				result.push_back(tmp.at(i)->getStmtNumber());
				found = true;
			}
		}
	}

	if(isAffected){
		for(size_t i = 0; i < result.size(); i++){
			if(updateVar){
				patternholder.push_back(pair<int,int>(result[i],varIndex[i]));
			} else{
				patternholder.push_back(pair<int,int>(result[i],varTable.getIndex(param2_trim)));
			}
		}
	}

	if(mapper.find(param1_name) != mapper.end()){
		mapper[param1_name] = intersect(mapper[param1_name], result);
	} else{
		mapper.insert(pair<string,vector<int>>(param1_name, result));
	}

	if(updateVar){
		if(mapper.find(param2_name) != mapper.end()){
			mapper[param2_name] = intersect(mapper[param2_name], varIndex);
		} else{
			mapper.insert(pair<string,vector<int>>(param2_name, varIndex));
		}
	}

	return found;
}

bool QueryEvaluator::updateMap(VAR_TYPE param1_type, string param1_name, VAR_TYPE param2_type, string param2_name, vector<pair<int,int>>& res){

	if(res.size() == 0){ return false;}

	if(param1_type != Q_STMT_LITERAL && param1_type != Q_ANY_VALUE && param1_type != Q_VAR_LITERAL &&
		param2_type != Q_STMT_LITERAL && param2_type != Q_ANY_VALUE && param2_type != Q_VAR_LITERAL){
		vector<pair<int,int>>::iterator itr = res.begin();

		while(itr != res.end()){
			vector<int> tmp1 = getInitialResultList(param1_type);
			vector<int> tmp2 = getInitialResultList(param2_type);

			int curr1 = itr->first;
			int curr2 = itr->second;

			if(!binary_search(tmp1.begin(), tmp1.end(), curr1) || !binary_search(tmp2.begin(), tmp2.end(), curr2)){
				itr = res.erase(itr);
			} else{
				itr++;
			}
		}
	}

	if(param1_type != Q_STMT_LITERAL && param1_type != Q_ANY_VALUE && param1_type != Q_VAR_LITERAL){
		vector<int> ls;
		for(size_t i = 0; i < res.size(); i++){
			//cout << res.at(i).first << endl;
			ls.push_back(res.at(i).first);
		}
		if(mapper.find(param1_name) != mapper.end()){ //do a union here
			vector<int> ori = mapper[param1_name];
			mapper[param1_name] = intersect(ori,ls);
		} else{
			mapper.insert(pair<string,vector<int>>(param1_name, ls));
		}
	}

	if(param2_type != Q_STMT_LITERAL && param2_type != Q_ANY_VALUE && param2_type != Q_VAR_LITERAL){
		vector<int> ls;
		for(size_t i = 0; i < res.size(); i++){
			ls.push_back(res.at(i).second);
		}
		if(mapper.find(param2_name) != mapper.end()){ //do a union here
			vector<int> ori = mapper[param2_name];
			mapper[param2_name] = intersect(ori,ls);
		} else{
			mapper.insert(pair<string,vector<int>>(param2_name, ls));
		}
	}
	
	return true;
}

vector<int> QueryEvaluator::intersect(vector<int> vect1, vector<int> vect2){
	set<int> tmp;

	for(size_t i = 0; i < vect1.size(); i++){
		if(find(vect2.begin(), vect2.end(), vect1[i]) != vect2.end()){
			tmp.insert(vect1[i]);
		}
	}

	for(size_t i = 0; i < vect2.size(); i++){
		if(find(vect1.begin(), vect1.end(), vect2[i]) != vect1.end()){
			tmp.insert(vect2[i]);
		}
	}

	vector<int> result(tmp.begin(), tmp.end());
	return result;
}

vector<pair<int, int>> QueryEvaluator::evaluateSuchThat(Query *query){
	QueryType qt = query->getQueryType();
	Synonym* param1 = query->getFirstArgument();
	string param1_name = param1->getVariable();
	VAR_TYPE param1_type = param1->getVarType();

	Synonym* param2 = query->getSecondArgument();
	string param2_name = param2->getVariable();
	VAR_TYPE param2_type = param2->getVarType();
	if(param2_type == Q_VAR_LITERAL){ //strip the quotes
		param2_name = param2_name.substr(1,param2_name.length()-2);
	}

	int arg1, arg2;

	switch(qt){
	case MODIFIES:
		arg1 = ANYVALUE, arg2 = ANYVALUE;
		if(param1_type == Q_STMT_LITERAL){	arg1 = stoi(param1_name);}
		if(param2_type == Q_VAR_LITERAL){	
			arg2 = varTable.getIndex(param2_name);
			if(arg2 == -1) // no such variables
				throw CustomException(PQL_PARSE_ERROR, "Variable doesn't exist");
		}
		return modifies.getModifiesStatement(arg1, arg2);
		break;
	case USES:
		arg1 = ANYVALUE, arg2 = ANYVALUE;
		if(param1_type == Q_STMT_LITERAL){	arg1 = stoi(param1_name);}
		if(param2_type == Q_VAR_LITERAL){	
			arg2 = varTable.getIndex(param2_name);
			if(arg2 == -1) // no such variables
				throw CustomException(PQL_PARSE_ERROR, "Variable doesn't exist");
		}
		return uses.getUsesStatement(arg1, arg2);
		break;
	case PARENT:
		arg1 = ANYVALUE, arg2 = ANYVALUE;
		if(param1_type == Q_STMT_LITERAL){	arg1 = stoi(param1_name);}
		if(param2_type == Q_STMT_LITERAL){	arg2 = stoi(param2_name);}
		return parent.getParent(arg1, arg2);
		break;
	case PARENTSTAR:
		arg1 = ANYVALUE, arg2 = ANYVALUE;
		if(param1_type == Q_STMT_LITERAL){	arg1 = stoi(param1_name);}
		if(param2_type == Q_STMT_LITERAL){	arg2 = stoi(param2_name);}
		return parent.getParentStar(arg1, arg2);
		break;
	case FOLLOWS:
		arg1 = ANYVALUE, arg2 = ANYVALUE;
		if(param1_type == Q_STMT_LITERAL){	arg1 = stoi(param1_name);}
		if(param2_type == Q_STMT_LITERAL){	arg2 = stoi(param2_name);}
		return follows.getFollows(arg1, arg2);
		break;
	case FOLLOWSSTAR:
		arg1 = ANYVALUE, arg2 = ANYVALUE;
		if(param1_type == Q_STMT_LITERAL){	arg1 = stoi(param1_name);}
		if(param2_type == Q_STMT_LITERAL){	arg2 = stoi(param2_name);}
		return follows.getFollowsStar(arg1, arg2);
		break;
	default:
		vector<pair<int,int>> stub;
		return stub;
	}
}

vector<int> QueryEvaluator::getInitialResultList(VAR_TYPE vt){
	set<int> result;

	switch(vt){
	case Q_ASSIGN:
	{
		vector<Node*> tmp = deTable->getAssignDe();
		for(size_t i = 0; i < tmp.size(); i++){
			Node *n = tmp.at(i);
			result.insert(n->getStmtNumber());
		}
		break;
	}
	case Q_STATEMENT:
	{
		vector<Node*> tmp = deTable->getAssignDe();
		for(size_t i = 0; i < tmp.size(); i++){
			Node *n = tmp.at(i);
			result.insert(n->getStmtNumber());
		} //fall through. stmt consist of assign + while
	}
	case Q_WHILE:
	{
		vector<Node*> tmp = deTable->getWhileDe();
		for(size_t i = 0; i < tmp.size(); i++){
			Node *n = tmp.at(i);
			result.insert(n->getStmtNumber());
		}
		break;
	}
	case Q_VARIABLE:
	{
		int size = varTable.size();
		for(int i = 0; i < size; i++){
			result.insert(i);
		}
		break;
	}
	case Q_CONSTANT:
	{
		vector<Node*> tmp = deTable->getConstantDe();
		for(size_t i = 0; i < tmp.size(); i++){
			Node *n = tmp.at(i);
			DesignEntity *de = n->getDesignEntity();
			Constant *c = dynamic_cast<Constant*>(de);
			int c_val = c->getValue();
			result.insert(c_val);
		}
		break;
	}
	case Q_PROGRAM_LINE:
	{
		vector<Node*> tmp = deTable->getAssignDe();
		vector<Node*> tmp2 = deTable->getWhileDe();
		int sum = tmp.size() + tmp2.size();

		for(int i = 1; i <= sum; i++){
			result.insert(i);
		}
		break;
	}
	default:
		break;
	}
	vector<int> out (result.begin(), result.end());
	sort(out.begin(), out.end());
	return out;
}

vector<int> QueryEvaluator::combineQueries(vector<pair<int,int>> &table1, vector<pair<int,int>> &table2, int join1, int join2) {

	int value1;
	int value2;
	vector<int> result;
	for (int i = 0; i<table1.size(); i++) {
		if (join1) {
			value1=table1[i].second;
		} else {
			value1=table1[i].first;
		}
		for (int j = 0; j<table2.size(); j++) {
			if (join2) {
				value2=table2[j].second;
			} else {
				value2=table2[j].first;
			}
			if (value1==value2) {
				if (join1) {
					result.push_back(table1[i].first);
				} else {
					result.push_back(table1[i].second);
				}
				break;
			}
		}

	}
	return result;
}

vector<int> QueryEvaluator::combineQueriesIdentical(vector<pair<int,int>> &table1, vector<pair<int,int>> &table2, int join1, int join2) {
	int value1;
	int value2;
	int value1b;
	int value2b;
	vector<int> result;
	for (int i = 0; i<table1.size(); i++) {
		if (join1) {
			value1=table1[i].second;
			value1b=table1[i].first;
		} else {
			value1=table1[i].first;
			value1b=table1[i].second;
		}
		for (int j = 0; j<table2.size(); j++) {
			if (join2) {
				value2=table2[j].second;
				value2b=table2[j].first;
			} else {
				value2=table2[j].first;
				value2b=table2[j].second;
			}
			if ((value1==value2) && (value1b==value2b)) {
				if (join1) {
					result.push_back(table1[i].first);
				} else {
					result.push_back(table1[i].second);
				}
				break;
			}
		}

	}
	return result;
}

QueryEvaluator::~QueryEvaluator() {
	
}